﻿using System;
using System.IO;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using LisenceUtils.Enums;

namespace LisenceUtils
{
    public class MyLicense : LicenseEntity
    {
        private MyLicense()
        {
            AppName = ConstInfo.AppName;
        }

        public static MyLicense Default { get; } = new MyLicense();

        [ShowInLicenseInfo(true, "证书1", ShowInLicenseInfoAttribute.FormatType.String)]
        public bool EnableFeature01 { get; set; }


        [ShowInLicenseInfo(true, "证书2", ShowInLicenseInfoAttribute.FormatType.String)]
        public bool EnableFeature02 { get; set; }


        [ShowInLicenseInfo(true, "证书3", ShowInLicenseInfoAttribute.FormatType.String)]
        public bool EnableFeature03 { get; set; }

        private LicenseStatus GetLicStatus(out string validationMsg, LicenseEntity lic)
        {
            LicenseStatus licStatus;
            validationMsg = string.Empty;

            switch (lic.Type)
            {
                case LicenseTypes.Single:
                    var uid = OnGetUid();
                    if (lic.Uid == uid)
                    {
                        licStatus = LicenseStatus.Valid;
                    }
                    else
                    {
                        validationMsg = "密钥不匹配";
                        licStatus = LicenseStatus.Invalid;
                    }
                    break;
                default:
                    validationMsg = "无效的密钥";
                    licStatus = LicenseStatus.Invalid;
                    break;
            }

            return licStatus;
        }

        public override string GenerateLicenseBase64String(byte[] certPrivateKeyData, SecureString certFilePwd)
        {
            //Serialize license object into XML                    
            var licenseObject = new XmlDocument();
            using (var writer = new StringWriter())
            {
                var serializer = new XmlSerializer(typeof (LicenseEntity), new[] {GetType()});
                serializer.Serialize(writer, this);
                licenseObject.LoadXml(writer.ToString());
            }

            //Get RSA key from certificate
            var cert = new X509Certificate2(certPrivateKeyData, certFilePwd);
            var rsaKey = (RSACryptoServiceProvider) cert.PrivateKey;

            //Sign the XML
            SignXml(licenseObject, rsaKey);

            //Convert the signed XML into BASE64 string            
            return Convert.ToBase64String(Encoding.UTF8.GetBytes(licenseObject.OuterXml));
        }

        private static void SignXml(XmlDocument xmlDoc, RSA key)
        {
            // Check arguments.
            if (xmlDoc == null)
                throw new ArgumentException("xmlDoc");
            if (key == null)
                throw new ArgumentException("key");

            // Create a SignedXml object.
            var signedXml = new SignedXml(xmlDoc);

            // Add the key to the SignedXml document.
            signedXml.SigningKey = key;

            // Create a reference to be signed.
            var reference = new Reference();
            reference.Uri = "";

            // Add an enveloped transformation to the reference.
            var env = new XmlDsigEnvelopedSignatureTransform();
            reference.AddTransform(env);

            // Add the reference to the SignedXml object.
            signedXml.AddReference(reference);

            // Compute the signature.
            signedXml.ComputeSignature();

            // Get the XML representation of the signature and save
            // it to an XmlElement object.
            var xmlDigitalSignature = signedXml.GetXml();

            // Append the element to the XML document.
            xmlDoc.DocumentElement?.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
        }

        public override LicenseEntity ParseLicenseFromBase64String(string licenseString, byte[] certPubKeyData)
        {
            ValidationMsg = string.Empty;
            LicenseEntity license = null;
            if (string.IsNullOrWhiteSpace(licenseString))
            {
                LicenseStatus = LicenseStatus.Cracked;
                return null;
            }
            try
            {
                //Get RSA key from certificate
                var cert = new X509Certificate2(certPubKeyData);
                var rsaKey = (RSACryptoServiceProvider) cert.PublicKey.Key;
                var xmlDoc = new XmlDocument();

                // Load an XML file into the XmlDocument object.
                xmlDoc.PreserveWhitespace = true;
                xmlDoc.LoadXml(Encoding.UTF8.GetString(Convert.FromBase64String(licenseString)));

                // Verify the signature of the signed XML.            
                if (VerifyXml(xmlDoc, rsaKey))
                {
                    var nodeList = xmlDoc.GetElementsByTagName("Signature");
                    xmlDoc.DocumentElement?.RemoveChild(nodeList[0]);
                    var licXml = xmlDoc.OuterXml;

                    //Deserialize license
                    var serializer = new XmlSerializer(typeof (LicenseEntity), new[] {typeof (MyLicense)});
                    using (var reader = new StringReader(licXml))
                    {
                        license = (LicenseEntity) serializer.Deserialize(reader);
                    }
                    string validationMsg;
                    LicenseStatus = GetLicStatus(out validationMsg, license);
                    ValidationMsg = validationMsg;
                }
                else
                {
                    LicenseStatus = LicenseStatus.Invalid;
                }
            }
            catch
            {
                LicenseStatus = LicenseStatus.Cracked;
            }
            if (license != null)
            {
                license.LicenseStatus = LicenseStatus;
                license.ValidationMsg = ValidationMsg;
            }
            return license;
        }

        private static bool VerifyXml(XmlDocument doc, RSA key)
        {
            // Check arguments.
            if (doc == null)
                throw new ArgumentException("doc");
            if (key == null)
                throw new ArgumentException("key");

            // Create a new SignedXml object and pass it
            // the XML document class.
            var signedXml = new SignedXml(doc);

            // Find the "Signature" node and create a new
            // XmlNodeList object.
            var nodeList = doc.GetElementsByTagName("Signature");

            // Throw an exception if no signature was found.
            if (nodeList.Count <= 0)
            {
                throw new CryptographicException("验证失败");
            }

            // This example only supports one signature for
            // the entire XML document.  Throw an exception 
            // if more than one signature was found.
            if (nodeList.Count >= 2)
            {
                throw new CryptographicException(
                    "验证失败");
            }

            // Load the first <signature> node.  
            signedXml.LoadXml((XmlElement) nodeList[0]);

            // Check the signature and return the result.
            return signedXml.CheckSignature(key);
        }
    }
}